﻿using System;
using System.IO;
using System.Reflection;
using Sai.Core;
using Sai.Core.Rules;
using log4net;
using System.Xml.Serialization;
using Sai.Core.Reporting;

[assembly: log4net.Config.XmlConfiguratorAttribute(Watch = true)] 

namespace Sai.Console
{
	/// <summary>
	/// Main application class.
	/// </summary>
	public class Program
	{
		/// <summary>
		/// Internal logger - configured for log file and console output by app.config.
		/// </summary>
		private static readonly ILog logger = LogManager.GetLogger("ApplicationLog");

		/// <summary>
		/// Return code for main program.
		/// </summary>
		private const int NoError = 0;

		/// <summary>
		/// Return code for main program.
		/// </summary>
		private const int CommandLineError = -1;

		/// <summary>
		/// Return code for main program.
		/// </summary>
		private const int UnhandledExceptionError = 1;

		/// <summary>
		/// Entry point for console runner.
		/// </summary>
		/// <param name="args">Command line options.</param>
		/// <returns>Exit code - 0 if no errors in running, -1 for command line errors, +1 for unhandled exceptions.</returns>
		public static int Main(string[] args)
		{
			AssemblyName thisApplication = Assembly.GetExecutingAssembly().GetName();
			
			AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;

			var commandLineOptions = new CommandLineOptions(); 

			try
			{
				commandLineOptions.Parse(args);
			}
			catch (CommandLineException ex)
			{
				logger.ErrorFormat("{0}: {1}", thisApplication.Name, ex.Message);
				logger.Info(commandLineOptions.Usage(thisApplication.Name));

				return CommandLineError;
			}

			logger.InfoFormat(
							"{0} {1} ({2}-bit .NET {3})",
							thisApplication.Name,
							thisApplication.Version,
							IntPtr.Size * 8,
							Environment.Version);

            logger.Info("Reporting on: ");
            logger.InfoFormat("\t[{0}] Additions ", commandLineOptions.ReportOnAdditions ? "x" : " ");
            logger.InfoFormat("\t[{0}] Changes", commandLineOptions.ReportOnChanges ? "x" : " ");
            logger.InfoFormat("\t[{0}] Removals", commandLineOptions.ReportOnRemovals ? "x" : " ");
            logger.InfoFormat("\t[{0}] Obsolete Members and Types", commandLineOptions.ReportOnObsolete ? "x" : " ");   

			if (LogManager.GetRepository().Threshold != log4net.Core.Level.Debug)
			{
				LogManager.GetRepository().Threshold = (commandLineOptions.Quiet)
											? log4net.Core.Level.Error
											: log4net.Core.Level.Info;
			}

			// look for rules...
			IRulePluginFinder ruleFinder = new RulePluginReflectionFinder(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
			IRulePluginRepository repository = new RulePluginRepository();

			ruleFinder.Find(repository);

			ProductReleaseProjectBuilder builder = new ProductReleaseProjectBuilder();
			ProductReleaseProject project = null;

			if (!string.IsNullOrEmpty(commandLineOptions.ProjectFile))
			{
				project = builder.CreateFromFile(commandLineOptions.ProjectFile);
			}
			else
			{
				if (string.IsNullOrEmpty(commandLineOptions.OutputFolder))
				{
					commandLineOptions.OutputFolder = Path.Combine(
						System.Environment.GetFolderPath(
							System.Environment.SpecialFolder.LocalApplicationData), 
						"Sai");
				}

				project = builder.Create(commandLineOptions.ProductName,
											thisApplication.Version,
											commandLineOptions.FromVersion,
											commandLineOptions.FromVersionFolder,
											commandLineOptions.ToVersion,
											commandLineOptions.ToVersionFolder,
											commandLineOptions.OutputFolder,
											commandLineOptions.ReportFormat,
											commandLineOptions.Clean,
											commandLineOptions.SandcastleProject,
											commandLineOptions.TopicId,
                                            commandLineOptions.ReportOnRemovals,
                                            commandLineOptions.ReportOnAdditions,
                                            commandLineOptions.ReportOnChanges,
                                            commandLineOptions.ReportOnObsolete);
			}

			// now given project run it...
			var folderComparisonBuilder = new AssemblyComparisonBuilder(project);

			long startTicks = 0;

			folderComparisonBuilder.BuildStarting += (t, e) =>
			{
				startTicks = DateTime.Now.Ticks;
				logger.Debug("Started at " + DateTime.Now);
			};

			folderComparisonBuilder.LoadingFolder += (s, e) =>
			{
				logger.InfoFormat("Loading assemblies from {0}", e.FolderName);
			};

			folderComparisonBuilder.BuildError += (s, e) =>
			{
				logger.Error(e.Message);
			};

			folderComparisonBuilder.BuildComplete += (s, e) =>
			{
				long endTicks = DateTime.Now.Ticks;

				logger.Debug("Folder comparison finished after " + TimeSpan.FromTicks(endTicks - startTicks));
			};

            var progressIndicator = new NullProgressReporter();

			// comparison results needs packaging with summary info and changed assemblies to report on it.
            var comparisonResults = folderComparisonBuilder.Build(progressIndicator);
			
			IWalkTypeDifferencesFactory typeDifferenceWalkerFactory = new TypeDifferenceWalkerFactory(repository);
			IWalkIntrospectedAssembliesFactory assemblyDifferenceWalkerFactory = new AssemblyIntrospectionWalkerFactory(repository);
			IProductReleaseDifferenceFactory releaseDifferenceFactory = new ProductReleaseDifferenceFactory(project);

            IAssemblyDifferenceFactory assemblyDifferenceFactory = new AssemblyDifferenceFactory();

			var introspectionBuilder = new TypeIntrospectionBuilder(
										AssemblyIntrospectorFactory.Introspector,
										assemblyDifferenceWalkerFactory,
										typeDifferenceWalkerFactory,
										releaseDifferenceFactory,
										assemblyDifferenceFactory,
										comparisonResults,
                                        project);

			introspectionBuilder.AnalysingAssembly += (s, e) =>
			{
				logger.InfoFormat("Analysing assembly {0}", e.FileName);
			};

			introspectionBuilder.BuildError += (s, e) =>
			{
				logger.Error(e.Message);
			};

            introspectionBuilder.Build(progressIndicator);
						
			logger.InfoFormat("Creating reports in {0} format", project.ReportFormat.ToString());

			IReportFormatterFactory reportFactory = new ReportFormatterFactory(project);

			if (introspectionBuilder.ProductReleaseDifference != null)
			{
				var reporter = new ReportBuilder(
					introspectionBuilder.ProductReleaseDifference,
					introspectionBuilder.AssemblyDifferences,
					project,
					reportFactory);

				reporter.CreatingReport += (s, e) =>
				{
					logger.InfoFormat("Creating report for {0}", e.FileName);
				};
				
				reporter.BuildError += (s, e) =>
				{
					logger.Error(e.Message);
				};

				reporter.BuildEvent += (s, e) =>
				{
					// mainly for debugging...
					logger.Info(e.Message);
				};

				reporter.BuildComplete += (s, e) =>
				{
					logger.Info("Sai Done.");

					long endTicks = DateTime.Now.Ticks;

					TimeSpan duration = new TimeSpan(endTicks - startTicks);
					logger.Debug("Completed at " + DateTime.Now);
					logger.DebugFormat("Total Time {0}", duration);
				};

                reporter.Build(progressIndicator);
			}

			return NoError;
		}

		/// <summary>
		/// Top level exception handler for "unhandled" exceptions.
		/// </summary>
		/// <param name="sender">The sender of the exception.</param>
		/// <param name="e">The exception info.</param>
		static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			Exception ex = e.ExceptionObject as Exception;

			if (ex != null)
				System.Console.WriteLine(ex.ToString());
			else
				System.Console.WriteLine("Error of unknown type thrown in applicaton domain");

            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debugger.Break();
            }

			Environment.Exit(UnhandledExceptionError);
		}
	}
}
